Keyword: swift,swiftUI,ObservableObject
到Day12 使用swiftUI顯示Ktor的資料 放在這邊
KMMDay12
昨天用了Android顯示資料,今天就來用iOS顯示吧.今天大部分的時間都會在Xcode中進行.
首先...先在gradle(shared)內的iOS區添加Ktor的依賴,這是給iOS所使用的Ktor,加入後記得sync Gradle啊
...//gradle中
val iosMain by getting{
dependencies{
implementation(Develop.Ktor.ios)
}
}
...//Dependencies中
object Ktor{
...
val ios = "io.ktor:ktor-client-ios:${Versions.ktor}"
...
}
然後找到Android Studio 中找到Xcode專案的位置,如果沒有更改的話應該會在iosApp package底下,一個副檔名為.xcodeproj的檔案
在上面右鍵⇒ Open in ⇒ Xcode 使用Xcode打開.
原本iOS所使用的swift語言,並不能支持Kotlin獨特的suspend與coroutine機制,但好在經過KMM中間層的處理,讓這段東西下放到Kotlin原生來處理.
由於網路請求是一個耗時工作,現在我們要來實作一個可供觀察的資料來源,讓網路請求的結果被通知,在Android中昨天我們已經使用了LiveData來處理,而在iOS中,則是可以使用ObservableObject來作為資料來源.
很巧的是,這兩種解法殊途同歸,都是利用了觀察者模式.來降低資料層與畫面層之間的耦合關係.之後可以研究這兩段的程式碼,資料層的部分都是不知道自己是被如何使用.只盡責地在資料變化時發布改變的消息出去,而接收到的畫面層根據自己的需要來顯示.這點不只是在Android或是iOS,在KMM中也是隨處可見.
(今天的以下部分都將在Xcode上撰寫iOS專案,使用swift)
我們在iosApp資料夾上右鍵,打開選單,選擇New File
然後建立一個新的swift檔案
命名用被觀察的物件加上ViewModel字尾,以這次的CafeResponseItem為例,新的檔案名稱為"CafeResponseItemViewModel"
建立一個CafeResponseItemViewModel的物件 ,繼承ObservableObject,並且把KMM的shared import進來.
import Foundation
import shared
class CafeResponseItemViewModel: ObservableObject {
}
(其實Swift寫起來跟Kotlin很類似呢,所以 Kotlin經驗者在閱讀swift的語法上應該沒什麼問題)
把來自shared的DataRepository加入,注意swift和kotlin相比,多了一個let的關鍵字,而self接近Kotlin的this關鍵字
class CafeResponseItemViewModel: ObservableObject {
private let repository: DataRepository
init(repository: DataRepository) {
self.repository = repository
}
}
這樣我們就能夠在其中使用DataRepository了
然後把我們回傳的CafeResponseItem Array標記為@Published ,告訴其他觀察者這邊會發送資訊,請記得接收.
最後加上一個拉取資料的方法,讓CafeResponseItem的Array藉由Ktor去更新,整個class如下:
import Foundation
import shared
class CafeResponseItemViewModel: ObservableObject {
@Published var cafeResponseItemList = [CafeResponseItem]()
private let repository: DataRepository
init(repository: DataRepository) {
self.repository = repository
}
func fetch() {
repository.fetchCafesFromNetwork(cityName:"taipei"){ result , error in
if let result = result{
self.cafeResponseItemList = result
}
}
}
}
回到KMM專案一開始就建立好的ContentView.swift,這邊是使用swiftUI的格式,也就是所謂的用宣告式UI,最近越來越流行了,在Flutter與Android compose都能看到類似的組件.
import SwiftUI
import shared
struct ContentView: View {
let greet = Greeting().greeting()
var body: some View {
Text(greet)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PreviewProvider只是提供開發者的預覽功能,真正的View是在ContentView.
先把範例預先建立好的greet與Text都刪除,我們不需要了,然後放上我們剛剛建立好的CafeResponseItemViewModel
這邊要標記@ObservedObject,與剛剛的@Published對應,說明這是接收訂閱的一方.
struct ContentView: View {
@ObservedObject var cafeResponseItemViewModel = CafeResponseItemViewModel(repository:DataRepository())
var body: some View {
}
}
body的內容雖然可以直接把觀察到的資料放進去,但是iOS有提供相當美觀的NavigationView功能,可以提高使用者的體驗,我們來試用看看.
這邊先用一個簡單的文字來看看Navigation效果,將body的View的內容加入NavigationView
var body: some View {
NavigationView {
Text("Hi")
.navigationBarTitle(Text("CafeList"), displayMode: .large)
}
}
然後在View的onAppear,由cafeResponseItemViewModel進行網路請求,這是在View出現時會執行其中的內容
var body: some View {
NavigationView {
Text("Hi")
.navigationBarTitle(Text("CafeList"), displayMode: .large)
.onAppear(perform: {
self.cafeResponseItemViewModel.fetch()
})
}
}
使用List ,將請求回傳的List一一顯示
var body: some View {
NavigationView {
List(cafeResponseItemViewModel.cafeResponseItemList, id: \.id) { cafe in
Text(cafe.name)
}
.navigationBarTitle(Text("CafeList"), displayMode: .large)
.onAppear(perform: {
self.cafeResponseItemViewModel.fetch()
})
}
}
整體會像這樣
最後再把通用文字View替換成自定義的CafeItemView,整個ContentView.swift如下
import shared
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentView: View {
@ObservedObject var cafeResponseItemViewModel = CafeResponseItemViewModel(repository:DataRepository())
var body: some View {
NavigationView {
List(cafeResponseItemViewModel.cafeResponseItemList, id: \.id) { cafe in
CafeItemView(cafeResponseItem: cafe)
}
.navigationBarTitle(Text("CafeList"), displayMode: .large)
.onAppear(perform: {
self.cafeResponseItemViewModel.fetch()
})
}
}
}
struct CafeItemView : View {
var cafeResponseItem: CafeResponseItem
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(cafeResponseItem.name).font(.headline)
Text(cafeResponseItem.id).font(.subheadline)
}
}
}
}
跑起來看看
Ktor的資料成功顯示在iOS畫面上了!
明天將會研究,如果有各平台不同的實作方法,例如最常用的Log在iOS與Android就不相同,這種情況應該如何去撰寫